/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.web.spring.web.bind;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.vacoor.nothing.common.util.StringUtils;
import org.vacoor.nothing.web.domain.Pageable;
import org.vacoor.nothing.web.domain.Sort;
import org.vacoor.nothing.web.domain.support.PageRequest;

/**
 * {@link Pageable} 解析器, 参考 Spring Data Commons
 * page=1&size=20
 * <p/>
 * 可以通过 {@link Qualifier} 来过滤特定的 page, size
 * eg:
 * t(@Qualifier("u") Pageable pageable) 将从 u_page, u_size 提取参数
 *
 * @author vacoor
 */
@SuppressWarnings({"unused"})
public class PageableHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
    private static final String DEFAULT_PAGE_PARAMETER = "page";
    private static final String DEFAULT_SIZE_PARAMETER = "size";
    private static final String DEFAULT_QUALIFIER_DELIMITER = "_";
    private static final int DEFAULT_MAX_PAGE_SIZE = Integer.MAX_VALUE;

    private static final Pageable DEFAULT_FALLBACK_PAGEABLE = new PageRequest();

    private SortHandlerMethodArgumentResolver sortResolver;
    private Pageable fallbackPageable = DEFAULT_FALLBACK_PAGEABLE;

    private String pageParameterName = DEFAULT_PAGE_PARAMETER;
    private String sizeParameterName = DEFAULT_SIZE_PARAMETER;
    private String qualifierDelimiter = DEFAULT_QUALIFIER_DELIMITER;
    private int maxPageSize = DEFAULT_MAX_PAGE_SIZE;

    public PageableHandlerMethodArgumentResolver() {
        this.sortResolver = new SortHandlerMethodArgumentResolver();
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return Pageable.class.equals(parameter.getParameterType());
    }

    @Override
    public Pageable resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

        // assert
        Pageable defaultPageable = parameter.hasParameterAnnotation(PageableDefault.class) ? getDefaultPageableFrom(parameter) : fallbackPageable;

        // 从参数中获取
        String pageStr = webRequest.getParameter(getParameterName(pageParameterName, parameter));
        String sizeStr = webRequest.getParameter(getParameterName(sizeParameterName, parameter));
        int page = parseAndApplyBoundaries(pageStr, 0, Integer.MAX_VALUE);
        int size = parseAndApplyBoundaries(sizeStr, 0, Integer.MAX_VALUE);
        Sort defaultSort = defaultPageable.getSort();

        page = page > 0 ? page : defaultPageable.getPage();
        size = size > 0 ? size : defaultPageable.getPageSize();
        size = size > maxPageSize ? maxPageSize : size;

        boolean mergeSort = parameter.hasParameterAnnotation(PageableDefault.class) && parameter.getParameterAnnotation(PageableDefault.class).mergeSort();
        Sort sort = sortResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);

        sort = sort == null || sort.size() == 0 ? defaultSort : (!mergeSort ? sort : sort.and(defaultSort));

        return new PageRequest(page, size, sort);
    }

    /**
     * 根据方法参数注解构建默认 {@link Pageable}
     *
     * @param parameter
     * @return
     */
    Pageable getDefaultPageableFrom(MethodParameter parameter) {
        PageableDefault defaults = parameter.getParameterAnnotation(PageableDefault.class);

        int page = defaults.page();
        int pageSize = defaults.size();
        Sort sort = parseSortDefinition(defaults.sort());

        if (pageSize < 1) {
            throw new IllegalStateException("page size < 1");
        }

        return new PageRequest(page, pageSize, sort);
    }

    /**
     * 获取参数名称
     *
     * @param source
     * @param parameter
     * @return
     */
    public String getParameterName(String source, MethodParameter parameter) {
        StringBuilder segment = new StringBuilder();
        Qualifier qualifier;
        if (parameter != null && (qualifier = parameter.getParameterAnnotation(Qualifier.class)) != null) {
            segment.append(qualifier.value());
            segment.append(qualifierDelimiter);
        }
        segment.append(source);
        return segment.toString();
    }

    // 在给定范围中解析, 如果不能解析返回最小值
    private static int parseAndApplyBoundaries(String value, int lower, int upper) {

        try {
            int parsed = Integer.parseInt(value);
            return parsed < lower ? lower : parsed > upper ? upper : parsed;
        } catch (NumberFormatException e) {
            return lower;
        }
    }

    /**
     * 解析 name-asc 形式的排序定义
     *
     * @param orders
     * @return
     */
    private static final char SORT_DEFINITION_DELIMITER = '-';
    private static final Sort.Direction SORT_DEFINITION_FALLBAK_DIRECTION = Sort.Direction.ASC;

    protected Sort parseSortDefinition(String[] orders) {
        Sort sort = new Sort();
        for (String order : orders) {
            if (!StringUtils.hasText(order)) {
                continue;
            }

            int idx = order.lastIndexOf(SORT_DEFINITION_DELIMITER);

            Sort.Direction direction = null;
            if (idx > 0 && idx < order.length() - 1) {
                direction = Sort.Direction.forName(order.substring(idx + 1));
            }
            direction = direction != null ? direction : SORT_DEFINITION_FALLBAK_DIRECTION;
            String property = idx > 0 ? order.substring(0, idx) : order;

            sort.add(direction, property);
        }
        return sort;
    }

    public SortHandlerMethodArgumentResolver getSortResolver() {
        return sortResolver;
    }

    public void setSortResolver(SortHandlerMethodArgumentResolver sortResolver) {
        this.sortResolver = sortResolver;
    }

    public Pageable getFallbackPageable() {
        return fallbackPageable;
    }

    public void setFallbackPageable(Pageable fallbackPageable) {
        this.fallbackPageable = fallbackPageable != null ? fallbackPageable : DEFAULT_FALLBACK_PAGEABLE;
    }

    public String getPageParameterName() {
        return pageParameterName;
    }

    public void setPageParameterName(String pageParameterName) {
        this.pageParameterName = pageParameterName != null ? pageParameterName : DEFAULT_PAGE_PARAMETER;
    }

    public String getSizeParameterName() {
        return sizeParameterName;
    }

    public void setSizeParameterName(String sizeParameterName) {
        this.sizeParameterName = sizeParameterName != null ? sizeParameterName : DEFAULT_SIZE_PARAMETER;
    }

    public int getMaxPageSize() {
        return maxPageSize;
    }

    public void setMaxPageSize(int maxPageSize) {
        this.maxPageSize = maxPageSize > 0 ? maxPageSize : DEFAULT_MAX_PAGE_SIZE;
    }

    public String getQualifierDelimiter() {
        return qualifierDelimiter;
    }

    public void setQualifierDelimiter(String qualifierDelimiter) {
        this.qualifierDelimiter = qualifierDelimiter != null ? qualifierDelimiter : DEFAULT_QUALIFIER_DELIMITER;
    }
}
